// Package metrics provides metrics collection and monitoring functionality
package metrics

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"runtime"
	"sync"
	"time"

	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promauto"
	"github.com/prometheus/client_golang/prometheus/promhttp"
	"github.com/smart-snapshotter/internal/logging"
	"github.com/smart-snapshotter/pkg/types"
)

// MetricsCollector defines the interface for metrics collection
type MetricsCollector interface {
	// Counter operations
	IncrementCounter(name string, labels ...string)
	AddCounter(name string, value float64, labels ...string)

	// Gauge operations
	SetGauge(name string, value float64, labels ...string)
	AddGauge(name string, value float64, labels ...string)

	// Histogram operations
	RecordHistogram(name string, value float64, labels ...string)

	// Summary operations
	RecordSummary(name string, value float64, labels ...string)

	// System metrics
	RecordSystemMetrics()

	// Health check
	Health(ctx context.Context) error
}

// PrometheusCollector implements MetricsCollector using Prometheus
type PrometheusCollector struct {
	config       *types.MetricsConfig
	registry     *prometheus.Registry
	counters     map[string]*prometheus.CounterVec
	gauges       map[string]*prometheus.GaugeVec
	histograms   map[string]*prometheus.HistogramVec
	summaries    map[string]*prometheus.SummaryVec
	mu           sync.RWMutex
	server       *http.Server
	startTime    time.Time
}

// NewMetricsCollector creates a new metrics collector
func NewMetricsCollector(config *types.MetricsConfig) (MetricsCollector, error) {
	if config == nil {
		config = &types.MetricsConfig{
			Enabled: true,
			Port:    29090,
			Path:    "/metrics",
		}
	}

	registry := prometheus.NewRegistry()

	// Register standard Go metrics
	registry.MustRegister(prometheus.NewGoCollector())
	registry.MustRegister(prometheus.NewProcessCollector(prometheus.ProcessCollectorOpts{}))

	collector := &PrometheusCollector{
		config:     config,
		registry:   registry,
		counters:   make(map[string]*prometheus.CounterVec),
		gauges:     make(map[string]*prometheus.GaugeVec),
		histograms: make(map[string]*prometheus.HistogramVec),
		summaries:  make(map[string]*prometheus.SummaryVec),
		startTime:  time.Now(),
	}

	// Initialize predefined metrics
	if err := collector.initializeMetrics(); err != nil {
		return nil, fmt.Errorf("failed to initialize metrics: %w", err)
	}

	// Start HTTP server if enabled
	if config.Enabled {
		if err := collector.startServer(); err != nil {
			return nil, fmt.Errorf("failed to start metrics server: %w", err)
		}
	}

	return collector, nil
}

// initializeMetrics creates predefined metrics
func (c *PrometheusCollector) initializeMetrics() error {
	// Snapshot operations
	c.counters["snapshot_operations_total"] = promauto.With(c.registry).NewCounterVec(
		prometheus.CounterOpts{
			Name: "snapshot_operations_total",
			Help: "Total number of snapshot operations",
		},
		[]string{"operation", "status"},
	)

	// Deduplication metrics
	c.counters["dedup_files_total"] = promauto.With(c.registry).NewCounterVec(
		prometheus.CounterOpts{
			Name: "dedup_files_total",
			Help: "Total number of files processed by deduplication",
		},
		[]string{"type"}, // "unique", "duplicate"
	)

	c.counters["dedup_blocks_total"] = promauto.With(c.registry).NewCounterVec(
		prometheus.CounterOpts{
			Name: "dedup_blocks_total",
			Help: "Total number of blocks processed by deduplication",
		},
		[]string{"type"}, // "unique", "duplicate"
	)

	c.gauges["dedup_space_saved_bytes"] = promauto.With(c.registry).NewGaugeVec(
		prometheus.GaugeOpts{
			Name: "dedup_space_saved_bytes",
			Help: "Space saved by deduplication in bytes",
		},
		[]string{},
	)

	// Cache metrics
	c.gauges["cache_hits_total"] = promauto.With(c.registry).NewGaugeVec(
		prometheus.GaugeOpts{
			Name: "cache_hits_total",
			Help: "Total number of cache hits",
		},
		[]string{"cache_type"},
	)

	c.gauges["cache_misses_total"] = promauto.With(c.registry).NewGaugeVec(
		prometheus.GaugeOpts{
			Name: "cache_misses_total",
			Help: "Total number of cache misses",
		},
		[]string{"cache_type"},
	)

	c.gauges["cache_size_bytes"] = promauto.With(c.registry).NewGaugeVec(
		prometheus.GaugeOpts{
			Name: "cache_size_bytes",
			Help: "Current cache size in bytes",
		},
		[]string{"cache_type"},
	)

	c.gauges["cache_capacity_bytes"] = promauto.With(c.registry).NewGaugeVec(
		prometheus.GaugeOpts{
			Name: "cache_capacity_bytes",
			Help: "Maximum cache capacity in bytes",
		},
		[]string{"cache_type"},
	)

	// Loading metrics
	c.histograms["load_duration_seconds"] = promauto.With(c.registry).NewHistogramVec(
		prometheus.HistogramOpts{
			Name:    "load_duration_seconds",
			Help:    "Duration of load operations",
			Buckets: prometheus.DefBuckets,
		},
		[]string{"type", "status"},
	)

	c.counters["bytes_loaded_total"] = promauto.With(c.registry).NewCounterVec(
		prometheus.CounterOpts{
			Name: "bytes_loaded_total",
			Help: "Total bytes loaded",
		},
		[]string{"type"},
	)

	// Storage metrics
	c.gauges["storage_used_bytes"] = promauto.With(c.registry).NewGaugeVec(
		prometheus.GaugeOpts{
			Name: "storage_used_bytes",
			Help: "Storage space used in bytes",
		},
		[]string{"storage_type"},
	)

	c.counters["storage_operations_total"] = promauto.With(c.registry).NewCounterVec(
		prometheus.CounterOpts{
			Name: "storage_operations_total",
			Help: "Total number of storage operations",
		},
		[]string{"operation", "storage_type", "status"},
	)

	// System metrics
	c.gauges["system_uptime_seconds"] = promauto.With(c.registry).NewGaugeVec(
		prometheus.GaugeOpts{
			Name: "system_uptime_seconds",
			Help: "System uptime in seconds",
		},
		[]string{},
	)

	c.gauges["goroutines_count"] = promauto.With(c.registry).NewGaugeVec(
		prometheus.GaugeOpts{
			Name: "goroutines_count",
			Help: "Number of goroutines",
		},
		[]string{},
	)

	return nil
}

// startServer starts the HTTP metrics server
func (c *PrometheusCollector) startServer() error {
	mux := http.NewServeMux()
	promLogger := &prometheusLogger{}
	mux.Handle(c.config.Path, promhttp.HandlerFor(c.registry, promhttp.HandlerOpts{
		ErrorLog:      promLogger,
		ErrorHandling: promhttp.ContinueOnError,
	}))

	// Health check endpoint
	mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
		if err := c.Health(r.Context()); err != nil {
			w.WriteHeader(http.StatusServiceUnavailable)
			fmt.Fprintf(w, "unhealthy: %v", err)
			return
		}
		w.WriteHeader(http.StatusOK)
		fmt.Fprint(w, "healthy")
	})

	c.server = &http.Server{
		Addr:    fmt.Sprintf(":%d", c.config.Port),
		Handler: mux,
	}

	go func() {
		logging.Infof("Starting metrics server on port %d", c.config.Port)
		if err := c.server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			logging.Errorf("Metrics server error: %v", err)
		}
	}()

	return nil
}

// IncrementCounter increments a counter metric
func (c *PrometheusCollector) IncrementCounter(name string, labels ...string) {
	c.mu.RLock()
	defer c.mu.RUnlock()

	if counter, exists := c.counters[name]; exists {
		counter.WithLabelValues(labels...).Inc()
	}
}

// AddCounter adds a value to a counter metric
func (c *PrometheusCollector) AddCounter(name string, value float64, labels ...string) {
	c.mu.RLock()
	defer c.mu.RUnlock()

	if counter, exists := c.counters[name]; exists {
		counter.WithLabelValues(labels...).Add(value)
	}
}

// SetGauge sets a gauge metric value
func (c *PrometheusCollector) SetGauge(name string, value float64, labels ...string) {
	c.mu.RLock()
	defer c.mu.RUnlock()

	if gauge, exists := c.gauges[name]; exists {
		gauge.WithLabelValues(labels...).Set(value)
	}
}

// AddGauge adds a value to a gauge metric
func (c *PrometheusCollector) AddGauge(name string, value float64, labels ...string) {
	c.mu.RLock()
	defer c.mu.RUnlock()

	if gauge, exists := c.gauges[name]; exists {
		gauge.WithLabelValues(labels...).Add(value)
	}
}

// RecordHistogram records a histogram observation
func (c *PrometheusCollector) RecordHistogram(name string, value float64, labels ...string) {
	c.mu.RLock()
	defer c.mu.RUnlock()

	if histogram, exists := c.histograms[name]; exists {
		histogram.WithLabelValues(labels...).Observe(value)
	}
}

// RecordSummary records a summary observation
func (c *PrometheusCollector) RecordSummary(name string, value float64, labels ...string) {
	c.mu.RLock()
	defer c.mu.RUnlock()

	if summary, exists := c.summaries[name]; exists {
		summary.WithLabelValues(labels...).Observe(value)
	}
}

// RecordSystemMetrics records system-level metrics
func (c *PrometheusCollector) RecordSystemMetrics() {
	// Update uptime
	uptime := time.Since(c.startTime).Seconds()
	c.SetGauge("system_uptime_seconds", uptime)

	// Update goroutine count
	c.SetGauge("goroutines_count", float64(runtime.NumGoroutine()))
}

// Health performs health check
func (c *PrometheusCollector) Health(ctx context.Context) error {
	if !c.config.Enabled {
		return nil
	}

	// Check if metrics server is running
	if c.server != nil {
		// Simple health check - try to connect to the server
		addr := fmt.Sprintf(":%d", c.config.Port)
		conn, err := net.DialTimeout("tcp", addr, 2*time.Second)
		if err != nil {
			return fmt.Errorf("metrics server health check failed: %w", err)
		}
		conn.Close()
	}

	return nil
}

// Stop stops the metrics collector
func (c *PrometheusCollector) Stop(ctx context.Context) error {
	if c.server != nil {
		logging.Info("Stopping metrics server")
		return c.server.Shutdown(ctx)
	}
	return nil
}

// prometheusLogger implements promhttp.Logger
type prometheusLogger struct{}

func (l *prometheusLogger) Println(v ...interface{}) {
	logging.Error(fmt.Sprint(v...))
}

// Global metrics collector instance
var (
	globalMetrics MetricsCollector
	metricsOnce   sync.Once
)

// InitGlobalMetrics initializes the global metrics collector
func InitGlobalMetrics(config *types.MetricsConfig) error {
	var err error
	metricsOnce.Do(func() {
		globalMetrics, err = NewMetricsCollector(config)
	})
	return err
}

// GetMetrics returns the global metrics collector
func GetMetrics() MetricsCollector {
	if globalMetrics == nil {
		// Create a default metrics collector if not initialized
		collector, _ := NewMetricsCollector(nil)
		globalMetrics = collector
	}
	return globalMetrics
}

// Convenience functions for global metrics

// IncrementCounter increments a counter using the global metrics
func IncrementCounter(name string, labels ...string) {
	GetMetrics().IncrementCounter(name, labels...)
}

// AddCounter adds to a counter using the global metrics
func AddCounter(name string, value float64, labels ...string) {
	GetMetrics().AddCounter(name, value, labels...)
}

// SetGauge sets a gauge using the global metrics
func SetGauge(name string, value float64, labels ...string) {
	GetMetrics().SetGauge(name, value, labels...)
}

// AddGauge adds to a gauge using the global metrics
func AddGauge(name string, value float64, labels ...string) {
	GetMetrics().AddGauge(name, value, labels...)
}

// RecordHistogram records a histogram using the global metrics
func RecordHistogram(name string, value float64, labels ...string) {
	GetMetrics().RecordHistogram(name, value, labels...)
}

// RecordSummary records a summary using the global metrics
func RecordSummary(name string, value float64, labels ...string) {
	GetMetrics().RecordSummary(name, value, labels...)
}

// RecordSystemMetrics records system metrics using the global metrics
func RecordSystemMetrics() {
	GetMetrics().RecordSystemMetrics()
}